package sdk.pay;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.text.TextUtils;
import android.util.Base64;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import sdk.pay.constant.PayConfigure;
import sdk.pay.constant.PayConstant;
import sdk.pay.constant.PayExceptionType;
import sdk.pay.listener.Pay;
import sdk.pay.listener.PayGetPayStatusListener;
import sdk.pay.listener.PayGetPayTypeListener;
import sdk.pay.listener.PayUtilCallBack;
import sdk.pay.model.PayInfo;
import sdk.pay.model.PayParam;
import sdk.pay.model.TokenParam;
import sdk.pay.utils.PayAESUtil;
import sdk.pay.utils.PayExceptionUtil;
import sdk.pay.utils.PayLocationUtil;
import sdk.pay.utils.PayLogUtil;
import sdk.pay.utils.PayNetUtil;
import sdk.pay.utils.PayTimeStatisticsUtil;
import sdk.pay.utils.PayTimeUtil;
import sdk.pay.utils.PayToastUtil;
import sdk.pay.utils.pattern.AppidPattern;
import sdk.pay.utils.pattern.CodePattern;
import sdk.pay.utils.pattern.KeyPattern;
import sdk.pay.utils.pattern.SystemPattern;
import sdk.pay.utils.pattern.VectorPattern;

public abstract class PayHelper implements Pay {
    /**
     * to check init params valid
     * {@link #checkInitParams(String...)}
     */
    private boolean isValidInitParams;
    private static final String CHARSET_NAME = "UTF-8";
    static HashMap<String, PayParam> mArrayMap;
    private static OkHttpClient mOkHttpClient;
    static PayInfo mPayInfo;
    static TokenParam mTokenParam;
    PayTimeStatisticsUtil mPayTimeStatisticsUtil;
    PayTimeUtil mPayTimeUtil;
    HandlerThread mHandlerThread;
    Activity mActivity;
    Handler mPayHandler;
    SharedPreferences mSharedPreferences;
    PayUtilCallBack mCallBack;
    PayGetPayTypeListener mGetPayTypeListener;

    PayGetPayStatusListener mGetPayStatusListener;

    boolean mPayStatusCallBack;
    /*-payUtil action-*/
    static final String getServices = "GetServices";
    static final String stepPay = "stepPay";
    static final String getToken = "getToken";
    static final String getPayTypes = "getPayTypes";
    static final String getPayParams = "doGetParams";

    static final String getPayStatus = "getPayStatusl";
    static final int DO_GET_PAY_TYPE = 0X1;
    static final int DO_GET_PAY_PARAM = 0X1 << 1;
    static final int DO_SHOW_PAY_TYPE = 0X1 << 3;
    static final int DO_PAY = 0X1 << 2;
    static final int DO_GET_PAY_TOKEN = 0X1 << 4;
    static final int DO_GET_PAY_STATUS = 0X1 << 5;
    static final int DO_GET_SERVICES = 0X1 << 6;
    static final int DO_STEP_PAY = 0X1 << 7;
    /*-PayUtil types-*/
    static final int TYPE_WECHAT = 3;

    static final int TYPE_ALIPAY = 4;
    static final int TYPE_QQPAY = 11;
    static final int TYPE_JDPAY = 12;
    /*-PayUtil TaskStatus-*/
    static final String TASK_SUCCESS = "1";

    static final String TASK_Failure = "0";

    static {
        mOkHttpClient = new OkHttpClient.Builder()
                .connectTimeout(PayConstant.TIME_OUT, TimeUnit.SECONDS)
                .readTimeout(PayConstant.TIME_OUT, TimeUnit.SECONDS)
                .build();
        mArrayMap = new HashMap<>();
        mPayInfo = new PayInfo();
    }

    PayHelper(Activity activity, PayUtilCallBack callback, boolean payStatusCallBack) {
        if (activity == null) {
            throw new RuntimeException("construction param is error");
        }
        mActivity = activity;
        mCallBack = callback;
        mPayStatusCallBack = payStatusCallBack;

        mPayTimeUtil = new PayTimeUtil(mActivity);
        mPayTimeStatisticsUtil = new PayTimeStatisticsUtil(mActivity);
        mPayTimeStatisticsUtil.getTimeStatisticsModel().setDeviceType(PayConstant.DEVICE_TYPE_NAME);
        String netType = PayNetUtil.GetNetworkType(mActivity);
        mPayTimeStatisticsUtil.getTimeStatisticsModel().setOperator(netType);
        doGetLocation();

        mSharedPreferences = activity.getSharedPreferences(PayConstant.SHAREPERFERENCE_NAME, Context.MODE_PRIVATE);
    }

    /**
     * doGetLocation
     */
    private void doGetLocation() {
        PayLocationUtil.CityNameCallBack callBack = new PayLocationUtil.CityNameCallBack() {
            @Override
            public void transferCName(String cName) {
                mPayTimeStatisticsUtil.getTimeStatisticsModel().setArea(cName);
                PayLogUtil.logThird("cName=" + cName);
            }
        };
        PayLocationUtil payLocationUtil = new PayLocationUtil();
        payLocationUtil.getCityNameByApi(callBack);
    }

    /**
     * AES 加密
     *
     * @param tag  Tag
     * @param info infoMessage
     * @return String
     */
    String encryptAES(String tag, String info) {
        if (TextUtils.isEmpty(info)) {
            PayLogUtil.logSecond(tag + " encrypt info = null");
            showToast(PayExceptionType.INVALID_TOKEN.getMessage());
            return null;
        }
        byte[] cipherText = PayAESUtil.encrypt(info, mPayInfo.getKeyAES(), mPayInfo.getVectorAES());
        if (null == cipherText) {
            showToast(PayExceptionType.ENCRYPT_EXCEPTION.getMessage());
            return null;
        }
        String dest = Base64.encodeToString(cipherText, Base64.DEFAULT);
        // just replace special character "+" instead of "%2B"
        String str = dest.replace("+", "-").replace("/", "_").replace("=", "~").
                replace("\r", "").replace("\n", "");
        try {
            return URLEncoder.encode(str, CHARSET_NAME);
        } catch (UnsupportedEncodingException e) {
            PayLogUtil.logFourth(tag + " UnsupportedEncodingException = " + e.getMessage());
            showToast(PayExceptionType.DATA_EXCEPTION.getMessage());
            e.printStackTrace();
            PayExceptionUtil.getInstance().saveExceptionFile(tag +
                    " UnsupportedEncodingException e = " + e.getMessage());
            return null;
        }
    }

    /**
     * AES解密
     *
     * @param tag Tag
     * @param msg message
     * @return String
     * @throws UnsupportedEncodingException Exception
     */
    String decryptAES(String tag, String msg) throws UnsupportedEncodingException {
        msg = msg.replace("-", "+").replace("_", "/").replace("~", "=");
        if (TextUtils.isEmpty(msg)) {
            PayLogUtil.logFifth(tag + " onResponse msg = null");
            showToast(PayExceptionType.INVALID_TOKEN.getMessage());
            return null;
        } else {
            PayLogUtil.logFifth(tag + " onResponse msg = " + msg);
            byte[] decryptFrom = Base64.decode(msg, Base64.DEFAULT);
            byte[] decryptResult = PayAESUtil.decrypt(decryptFrom, mPayInfo.getKeyAES(), mPayInfo.getVectorAES());
            if (null == decryptResult) {
                PayLogUtil.logFifth(tag + " decryptResult exception");
                showToast(PayExceptionType.DECRYPT_EXCEPTION.getMessage());
                return null;
            }
            return new String(decryptResult, CHARSET_NAME);
        }
    }

    void clearPayUrl() {
        PayConfigure.PAY_URL = null;
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putInt(PayConstant.SHAREPERFERENCE_SERVCERS_SUM_KEY, 0);
        editor.putInt(PayConstant.SHAREPERFERENCE_SERVCERS_CURRENT_KEY, 0);
        editor.putString(PayConstant.SHAREPERFERENCE_MSG_KEY, null);
        editor.putString(PayConstant.SHAREPERFERENCE_NONCE_KEY, null);
        editor.apply();
    }

    boolean isJson(String str) {
        if (TextUtils.isEmpty(str))
            return false;
        try {
            new JSONObject(str);
            return true;
        } catch (JSONException e) {
            e.printStackTrace();
            PayLogUtil.logFourth("isJson JSONException = " + e.getMessage());
            PayExceptionUtil.getInstance().saveExceptionFile("isJson JSONException = " + e.getMessage());
            return false;
        }
    }

    boolean isJsonArray(String str) {
        if (TextUtils.isEmpty(str))
            return false;
        try {
            new JSONArray(str);
            return true;
        } catch (JSONException e) {
            e.printStackTrace();
            PayLogUtil.logFourth("isJsonArray JSONException = " + e.getMessage());
            PayExceptionUtil.getInstance().saveExceptionFile("isJson JSONException = " + e.getMessage());
            return false;
        }
    }

    void sendMessage(int what, int arg1) {
        Message message = new Message();
        message.arg1 = arg1;
        message.what = what;
        mPayHandler.sendMessage(message);
    }

    void sendEmptyMessageDelayed(int what, long delayMillis) {
        mPayHandler.sendEmptyMessageDelayed(what, delayMillis);
    }

    void sendEmptyMessage(int what) {
        mPayHandler.sendEmptyMessage(what);
    }

    boolean checkParams(String... params) {
        for (String param : params) {
            if (TextUtils.isEmpty(param)) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void showToast(final String message) {
        if (mCallBack != null) {
            mActivity.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mCallBack.onPayException(message);
                    PayToastUtil.showMessage(mActivity, message);
                }
            });
        }
    }

    void showPayTypeList() {
        if (null != mGetPayTypeListener) {
            mActivity.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mGetPayTypeListener.onPayDataResult();
                }
            });
        }
    }

    void showStatus(final int payStatus) {
        if (null != mGetPayStatusListener) {
            mActivity.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mGetPayStatusListener.onPayStatus(payStatus);
                }
            });
        }
    }

    @Override
    public void destroy() {
        destroyThread();
        destroyParams();
        destroyTimeStatisticsUtils();
    }

    private void destroyTimeStatisticsUtils() {
        if (null != mPayTimeStatisticsUtil) {
            mPayTimeStatisticsUtil.destroyTimeStatisticsUtils();
        }
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    private void destroyThread() {
        if (null != mHandlerThread) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                mHandlerThread.quitSafely();
            } else {
                mHandlerThread.quit();
            }
        }
    }

    private void destroyParams() {
        mTokenParam = null;
        mPayHandler.removeCallbacksAndMessages(null);
    }

    void getCall(final String tag, String url, final PayCallback callback) {
        mOkHttpClient.newCall(new Request.Builder().url(url).build()).enqueue(new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {
                PayLogUtil.logThird(tag + " onFailure IOException = " + e.getMessage());
                if (null != callback) {
                    callback.onFailure();
                }
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                PayLogUtil.logThird(tag + " onResponse code = " + response.code());
                if (null != callback) {
                    callback.onSuccess(call, response);
                }
                if (null != response.body()) {
                    if (!response.isSuccessful()) {
                        PayLogUtil.logThird(tag + " response body = null");
                    }
                } else {
                    PayLogUtil.logThird(tag + " onResponse failure");
                }
            }
        });
    }

    abstract void doGetServices(int type);

    abstract void getServices(int type, String url);

    abstract void getServicesFailure(int type);

    abstract void getToken();

    abstract void doGetToken(boolean flag);

    abstract void doStep(boolean flag);

    abstract void doGetTokenParams();

    abstract void getToken(String params);

    abstract void stepPay(String params);

    abstract void setNextApi();

    abstract void doGetPayType(boolean isFresh);

    abstract void getPayTypes(String url);

    abstract void doGetParams();

    abstract void getPayParam(String url, boolean secondPay);

    abstract void getPayStatus(String url);

    interface PayCallback {
        void onFailure();

        void onSuccess(Call call, Response response) throws IOException;
    }

    boolean checkInitParams(String... payParam) {
        if (payParam.length < 5) {
            throw new RuntimeException("payParam length under 5");
        }
        boolean isAppid = AppidPattern.matcher(payParam[0]);
        boolean isKey = KeyPattern.matcher(payParam[1]);
        boolean isVector = VectorPattern.matcher(payParam[2]);
        boolean isSystem = SystemPattern.matcher(payParam[3]);
        boolean isCode = CodePattern.matcher(payParam[4]);
        StringBuffer errorMsg = new StringBuffer();
        if (!isAppid)
            errorMsg.append("appid is error ");
        if (!isKey)
            errorMsg.append("key is error ");
        if (!isVector)
            errorMsg.append("vector is error ");
        if (!isSystem)
            errorMsg.append("system is error ");
        if (!isCode)
            errorMsg.append("code is error");
        if (!(isAppid && isKey && isVector && isSystem)) {
            showToast(errorMsg.toString());
            PayLogUtil.logFirst(errorMsg.toString());
            return false;
        }
        return true;
    }

    @Override
    public boolean isValidInitParams() {
        return isValidInitParams;
    }

    void setValidInitParams(boolean validInitParams) {
        isValidInitParams = validInitParams;
    }

    @Override
    public void setPaying(boolean isPaying) {
        mPayInfo.setIsPaying(isPaying);
    }

    public boolean getPaying() {
        return mPayInfo.getIsPaying();
    }
}
